const os = require("os");
var createError = require('http-errors');
var express = require('express');
var formData = require("express-form-data");
var path = require('path');
var fs = require("fs");
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');

var app = express();
// app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));


// parse data with connect-multiparty.
app.use(
  formData.parse({
    uploadDir: os.tmpdir(),
    autoClean: true
  })
);
// delete from the request all empty files (size == 0)
app.use(formData.format());
// change the file objects to fs.ReadStream
// app.use(formData.stream());
// union the body and the files
app.use(formData.union());

class WebResult {
  constructor(code, data, message) {
    this.code = code;
    this.data = data;
    this.message = message;
  }

  static success(data, message) {
    return new WebResult(0, data, message);
  }
  static fail(data, message) {
    return new WebResult(1, data, message);
  }
  static error(data, message) {
    return new WebResult(2, data, message);
  }
}

app.use('/', indexRouter);
app.post(
  "/upload/chunk",
  (function() {
    const uploadingFileWriteStreamMap = new Map();
    const uploadingFilesInfoMap = new Map();
    const fileLastChunkUploadTime = new Map();

    // 每十分钟清理一次
    setInterval(() => {
      for (const [key, value] of fileLastChunkUploadTime) {
        if (Date.now() - value > 1000 * 60 * 10) {
          const ws = uploadingFileWriteStreamMap.get(key);
          if (ws) {
            ws.end();
            uploadingFileWriteStreamMap.delete(key);
          }
          uploadingFilesInfoMap.delete(key);
          fileLastChunkUploadTime.delete(key);
        }
      }
    }, 1000 * 60 * 10);

    return async (request, response) => {
      response.setHeader("Content-Type", "application/json;charset=utf-8");
      const body = request.body;
      console.log(body);
      console.log("upload file:", body.name);
      console.log("上传文件块" + body.index);
      if (!body.name) {
        response.send(WebResult.error(undefined, "name is empty"));
        return;
      }

      if (body.index == 0 || body.index == 1) {
        // 创建输出目录
        const directoryPath = path.join(__dirname, "uploads");
        fs.mkdirSync(directoryPath, { recursive: true });
        const fileName = `${body.fileMD5}@${body.name}`;
        const filePath = path.join(__dirname, "uploads", fileName);
        if (fs.existsSync(filePath)) {
          response.send(WebResult.success({ finish: true }, "文件上传完毕"));
          return;
        }
        uploadingFileWriteStreamMap.set(body.name, fs.createWriteStream(filePath));
        uploadingFilesInfoMap.set(body.name, []);
      } else {
        // 文件块缺失
        if (!uploadingFileWriteStreamMap.has(body.name)) {
          response.send(WebResult.error(undefined, "上传失败，文件块缺失"));
          return;
        }
      }

      fileLastChunkUploadTime.set(body.name, Date.now());
      const infoArray = uploadingFilesInfoMap.get(body.name);
      if (infoArray[body.index]) {
        console.log("重复上传");
        response.send(WebResult.error({ finish: false }, "重复上传"));
        return;
      }
      const ws = uploadingFileWriteStreamMap.get(body.name);
      const cacheFileInfo = body.chunk;
      infoArray.push(cacheFileInfo);
      const chunkBuffer = fs.readFileSync(cacheFileInfo.path)
      if (infoArray.length >= body.total) {
        // 写出文件块
        ws.write(chunkBuffer);
        ws.end();
        uploadingFileWriteStreamMap.delete(body.name);
        uploadingFilesInfoMap.delete(body.name);
        fileLastChunkUploadTime.delete(body.name);
        console.log("文件", body.name, "上传完毕");
        response.send(WebResult.success({ finish: true }, "文件上传完毕"));
      } else {
        // 写出文件块
        ws.write(chunkBuffer);
        response.send(WebResult.success({ finish: false }, "chunk 上传完毕"));
      }
    }
  }())
);
app.get("/file/:fileName", (request, response) => {
  const { fileName } = request.params;
  if (!fileName) {
    response.send(WebResult.error(undefined, "fileName is empty"));
    return;
  }
  const filePath = path.join(__dirname, "uploads", fileName);
  if (fs.existsSync(filePath)) {
    const buffer = fs.readFileSync(filePath);
    response.write(
      buffer,
      "binary",
      () => {
        response.end();
      }
    );
  } else {
    response.send(WebResult.error(undefined, "文件不存在"));
  }
});
app.get("/files", (_request, response) => {
  const dp = path.join(__dirname, "uploads");
  const fileNames = fs.readdirSync(dp);
  const filesStat = fileNames.map(name => {
    const stat = fs.statSync(path.join(dp, name));
    const atIndex = name.indexOf("@");
    return {
      md5: name.substring(0, atIndex),
      name: name.substring(atIndex + 1),
      size: stat.size,
      time: stat.birthtime,
    };
  });
  response.send(WebResult.success(filesStat, "获取文件列表成功"));
});

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;
